/* Basic Flow for PCI Drivers - Example */

#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>

/* Module parameters */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;

/* Definition of the chip-specific record */
struct mychip {
    struct snd_card *card;
    struct pci_dev *pci;
    unsigned long port;
    int irq;
    unsigned long iobase_phys;
    void __iomem *iobase_virt;

};

/* Chip-specific destructor */
static int snd_mychip_free(struct mychip *chip)
{
    /* Release the irq */
    if (chip->irq >= 0)
        free_irq(chip->irq,chip);
    /* Release the I/O port & memory */
    if (chip->iobase_virt)
        iounmap(chip->iobase_virt);
    pci_release_regions(chip->pci);
    /* Disable the PCI entry */
    pci_disable_device(chip->pci);
    /* Release the data */
    kfree(chip);
    reutrn 0;
}

/* Component destructor */
static int snd_mychip_dev_free(struct snd_device *device)
{
    return snd_mychip_free(device->device_data);
}

/* Chip-specific constructor */
static int __devinit snd_mychip_create(struct snd_card *card,struct pci_dev *pci,struct mychip **rchip)
{
    struct mychip *chip;
    int err;
    static struct snd_device_ops ops = {
        .dev_free = snd_mychip_dev_free,
    };
    *rchip = NULL;
    /* Initialize the PCI entry */
    err = pci_enable_device(pci);
    if (err < 0)
        return err;
    /* Check PCI availability (28bit DMA) */
    if (pci_set_dma_mask(pci, DMA_28BIT_MASK) < 0 || pci_set_consistent_dma_mask(pci, DMA_28BIT_MASK) < 0) {
        printk(KERN_ERR, "error to set 28bit mask DMA\n");
        pci_disable_device(pci);
        return -ENXIO;
    }
    /* Allocate a chip-specific data with zero filled */
    chip = kzalloc(sizeof(*chip), GFP_KERNEL);
    if (chip == NULL)
        return -ENOMEM;
    /* Initialize the stuff */
    chip->card = card;
    chip->pci = pci;
    chip->irq = -1;
    
    /* PCI resource allocation */
    err = pci_request_regions(pci, "My Chip");
    if (err < 0) {
        kfree(chip);
        pci_disable_device(pci);
        return err;
    }
    chip->port = pci_resource_start(pci, 0);
    chip->iobase_phys = pci_resource_start(pci, 0);
    chip->iobase_virt = ioremap_nocache(chip->iobase_phys, pci_resource_len(pci,0));
    if (request_irq(pci->irq, snd_mychip_interrupt, IRQF_SHARED, "My Chip", chip)) {
        printk(KERN_ERR "cannot grab irq %d\n", pci->irq);
        snd_mychip_free(chip);
        return -EBUSY;
    chip->irq = pci->irq;

    err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
    if (err < 0) {
        snd_mychip_free(chip);
        return err;
    }

    snd_card_set_dev(card, &pci->dev);
    *rchip = chip;
    return 0;

}

/* Constructor */
static int __devinit snd_mychip_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
    static int dev;
    struct snd_card *card;
    struct mychip *chip;
    int err;

    if (dev >= SNDRV_CARDS)
        return -ENODEV;
    if (!enable[dev]) {
        dev++;
        return -ENOENT;
    }
    err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
    if (err < 0)
        return err;
    err = snd_mychip_create(card, pci, &chip);
    if (err < 0) {
        snd_card_free(card);
        return err;
    }
    strcpy(card->driver, "My Chip");
    strcpy(card->shortname, "My Own Chip 123");
    sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->ioport, chip->irq);

    err = snd_card_register(card);
    if (err < 0) {
        snd_card_free(card);
        return err;
    }

    pci_set_drvdata(pci, card);
    dev++;
    return 0;
}

/* Destructor */
static void __devexit snd_mychip_remove(struct pci_dev *pci)
{
    snd_card_free(pci_get drvdata(pci));
    pci_set_drvdata(pci, NULL);
}

/* PCI IDs */
static struct pci_device_id snd_mychip_ids[] = {
    { PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_BAR, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, }, ....{0,}
};

MODULE_DEVICE_TABLE(pci, snd_mychip_ids);

/* pci_driver definition */
static struct pci_driver driver = {
    .name = "My Own Chip",
    .id_table = snd_mychip_ids,
    .probe = snd_mychip_probe,
    .remove = __devexit_p(snd_mychip_remove),
};

/* PCM playback hardware definition */
static struct snd_pcm_hardware snd_mychip_playback_hw = {
    .info = (SNDRV_PCM_INFO_MMAP |
             SNDRV_PCM_INFO_INTERLEAVED |
             SNDRV_PCM_INFO_BLOCK_TRANSFER |
             SNDRV_PCM_INFO_MMAP_VALID),
    .formats =          SNDDRV_PCM_FMTBIT_S16_LE,
    .rates =            SNDDRV_PCM_RATE_8000_48000,
    .rate_min =         8000,
    .rate_max =         48000,
    .channels_min =     2,
    .channels_max =     2,
    .buffer_bytes_max = 32768,
    .period_bytes_min = 4096,
    .period_bytes_max = 32768,
    .period_min =       1,
    .peroid_max =       1024,
};

/* PCM capture hardware definition */
static struct snd_pcm_hardware snd_mychip_capture_hw = {
    .info = (SNDRV_PCM_INFO_MMAP |
             SNDRV_PCM_INFO_INTERLEAVED |
             SNDRV_PCM_INFO_BLOCK_TRANSFER |
             SNDRV_PCM_INFO_MMAP_VALID),
    .formats =          SNDRV_PCM_FMTBIT_S16_LE,
    .rates =            SNDRV_PCM_RATE_8000_48000,
    .rate_min =         8000,
    .rate_max =         48000,
    .channels_min =     2,
    .channels_max =     2,
    .buffer_bytes_max = 32768,
    .period_bytes_min = 4096,
    .period_bytes_max = 32768,
    .period_min =       1,
    .peroid_max =       1024,
};

/* Open playback callback */
static int snd_mychip_playback_open(struct snd_pcm_substream *substream)
{
    struct mychip *chip = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;
    runtime->hw = snd_mychip_playback_hw;
    return 0;
}

/* Close playback callback */
static int snd_mychip_playback_close(struct snd_pcm_substream *substream)
{
    struct mychip *chip = snd_pcm_substream_chip(substream);
    return 0;
}

/* Open capture callback */
static int snd_mychip_capture_open(struct snd_pcm_substream *substream)
{
    struct mychip *chip = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;
    runtime->hw = snd_mychip_capture_hw;
    return 0;
}

/* Cloase catpure callback */
static int snd_mychip_capture_close(struct snd_pcm_substream *substream)
{
    struct mychip *chip = snd_pcm_substream_chip(substream);
    return 0;
}

/* HW params callback */
static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
{
    return snd_pcm_lib_malloc_pages(substream,params_buffer_bytes(hw_params));
}

/* HW free callback */
static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream)
{
    return snd_pcm_lib_free_pages(substream);
}

/* Prepare callback */
static int snd_mychip_pcm_prepare(struct snd_pcm_substream *substream)
{
    struct mychip *chip = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;
    mychip_set_sample_format(chip, runtime->format);
    mychip_set_sample_rate(chip, runtime->rate);
    mychip_set_channels(chip,runtime->channels);
    mychip_set_dma_setup(chip,runtime->dma_addr, chip->buffer_size, chip->period_size);
    return 0;
}

/* Trigger callback */
static int snd_mychip_pcm_trigger(struct snd_pcm_substream *substream), int cmd)
{
    switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
            // Do something
            break;
        case SNDRV_PCM_TRIGGER_STOP:
            // Do something
            break;
        default:
            return -EINVAL;
    }
}

/* Pointer callback */
static snd_pcm_uframes_t snd_mychip_pcm_pointer(struct snd_pcm_substream *substream)
{
    struct mychip *chip = snd_pcm_substream_chip(substream);
    unsigned int current_ptr;
    current_ptr = mychip_get_hw_pointer(chip);
    return current_ptr;
}

/* Operators */
static struct snd_pcm_ops snd_mychip_playback_ops = {
    .open =             snd_mychip_playback_open,
    .close =            snd_mychip_playback_close,
    .ioctl =            snd_pcm_lib_ioctl,
    .hw_params =        snd_mychip_pcm_hw_params,
    .hw_free =          snd_mychip_pcm_hw_free,
    .prepare =          snd_mychip_pcm_prepare,
    .trigger =          snd_mychip_pcm_trigger,
    .pointer =          snd_mychip_pcm_pointer,
};


/* Operators */
static struct snd_pcm_ops snd_mychip_capture_ops = {
    .open =             snd_mychip_capture_open,
    .close =            snd_mychip_capture_close,
    .ioctl =            snd_pcm_lib_ioctl,
    .hw_params =        snd_mychip_pcm_hw_params,
    .hw_free =          snd_mychip_pcm_hw_free,
    .prepare =          snd_mychip_pcm_prepare,
    .trigger =          snd_mychip_pcm_trigger,
    .pointer =          snd_mychip_pcm_pointer,
};

/* Definitions of capture are ommitted here .. */

/* Create a pcm device */
static int __devinit snd_mychip_new_pcm(struct mychip *chip)
{
    struct snd_pcm *pcm;
    int err;
    err = snd_pcm_new(chip->card, "My Chip", 0, 1, 1, &pcm);
    if (err < 0)
        return err;
    pcm->private_data = chip;
    strcpy(pcm->name, "My Chip");
    chip->pcm = pcm;
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_mychip_playback_ops);
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_mychip_capture_ops);

    snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), 64*1024, 64*1024);
    return 0;
}



/* Module initialization */
static int __init alsa_card_mychip_init(void)
{
    return pci_register_driver(&driver);
}

/* Module clean up */
static void __exit alsa_card_mychip_exit(void)
{
    pci_unregister_driver(&driver);
}

module_init(alsa_card_mychip_init);
module_exit(alsa_card_mychip_exit);

EXPORT_NO_SYMBOLS;



